package com.sz.app.customer.api;

import com.sz.biz.app.dto.LoginDataDto;
import com.sz.biz.app.dto.TokenDto;
import com.sz.biz.app.web.security.Des;
import com.sz.biz.app.web.security.OnlineSessionManager;
import com.sz.biz.common.constants.MessagesCodes;
import com.sz.biz.common.customer.entity.CusCustomer;
import com.sz.biz.common.customer.entity.CusUser;
import com.sz.biz.common.customer.entity.CusUserLoginInfo;
import com.sz.biz.common.customer.service.CustomerService;
import com.sz.biz.common.customer.service.CustomerUserConfigService;
import com.sz.biz.common.customer.service.CustomerUserLoginInfoService;
import com.sz.biz.common.customer.service.CustomerUserService;
import com.sz.biz.common.customer.dto.CusUserConfigDto;
import com.sz.common.base.constants.UserActions;
import com.sz.common.base.dto.QResultDto;
import com.sz.common.base.dto.ResultDto;
import com.sz.common.base.exception.Exceptions;
import com.sz.common.base.i18n.I18nMessages;
import com.sz.common.base.logger.Logger;
import com.sz.common.core.service.Principal;
import com.sz.common.core.service.PrincipalLogUtils;
import com.sz.common.core.service.PrincipalUtils;
import com.sz.common.core.system.dto.FileInfoDto;
import com.sz.common.core.system.service.SysFileGroupService;
import com.sz.app.customer.constants.ModuleNames;
import com.sz.app.customer.api.system.dto.SecurityCenterDto;
import com.sz.app.customer.exception.CustomerAuthenticationException;
import com.sz.app.customer.exception.CustomerUpdatePasswordException;
import com.sz.app.customer.exception.ErrorCodes;
import com.sz.app.customer.web.security.SecurityKeys;
import com.sz.app.customer.api.system.dto.CusUserSimpleDto;
import com.sz.app.customer.api.system.dto.LoginResultDto;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.Subject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.ObjectUtils;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;
import java.io.Serializable;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@RestController
@RequestMapping("/api/v1/auth")
@Api(description = " ", tags = "000-0、安全登录")
public class SecurityController {

    public static final String UNKNOWN = "unknown";
    @Autowired
    private SecurityKeys securityKeys;

    @Autowired
    private OnlineSessionManager onlineSessionManager;

    @Autowired
    private CustomerUserService cusUserService;
    @Autowired
    private CustomerService customerService;

    @Autowired
    private CustomerUserConfigService customerUserConfigService;

    @Autowired
    SysFileGroupService fileGroupService;

    @Autowired
    private CustomerUserLoginInfoService customerUserLoginInfoService;

    @Autowired
    protected I18nMessages messageSource;

    protected Logger logger = Logger.getLogger(this.getClass());

    @RequestMapping(value = "/login", method = RequestMethod.POST)
    public ResultDto login(@RequestBody LoginDataDto loginDataDto, HttpServletRequest request) throws Exception {
        int errorTimes = 0;
        Session session = SecurityUtils.getSubject().getSession();
        if (session != null) {
            Object obj = session.getAttribute("login.error.times");
            if (obj != null && obj instanceof Integer) {
                errorTimes = (int) obj;
            }
            session.setAttribute("login.error.times", errorTimes + 1);
        }
        if (errorTimes > 0) {
            if (StringUtils.isNotEmpty(loginDataDto.getValidateCode())) {
                if (!checkVCode(loginDataDto.getValidateCode())) {
                    return ResultDto.createResult(ErrorCodes.ERROR_LOGIN_VALIDATE_CODE_ERR);
                }
            } else {
                return ResultDto.createResult(ErrorCodes.ERROR_LOGIN_VALIDATE_CODE_EMPTY);
            }
        }
        String username = loginDataDto.getUserName();
        String password = decPassword(loginDataDto.getPassWord());

        CusUserLoginInfo cusUserLoginInfo = new CusUserLoginInfo();
        cusUserLoginInfo.setUserName(username);
        cusUserLoginInfo.setUserCode(username);
        cusUserLoginInfo.setLoginTime(new Date());
        String cliIp = getIpAddr(request);
        cusUserLoginInfo.setLoginIp(cliIp);
        logger.debug("-----获取用户的IP地址为：" + cliIp + "--------------");
        SecurityUtils.getSubject().getSession().setAttribute("username", username);

        Subject subject = SecurityUtils.getSubject();
        UsernamePasswordToken token = new UsernamePasswordToken(username, password);
        try {
            logger.info("对用户[" + username + "]进行登录验证..验证开始");
            subject.login(token);
            logger.info("对用户[" + username + "]进行登录验证..验证通过");
            cusUserLoginInfo.setErrCode(0);
            cusUserLoginInfo.setLoginStatus(true);
            customerUserLoginInfoService.updateUserLoginInfo(cusUserLoginInfo);
        } catch (UnknownAccountException uae) {
            logger.warn("对用户[" + username + "]进行登录验证..验证未通过,账户已锁定");
            return ResultDto.createResult(ErrorCodes.ERROR_LOGIN_UNKNOWN_ACCOUNT);
        } catch (IncorrectCredentialsException ice) {
            logger.warn("对用户[" + username + "]进行登录验证..验证未通过,错误的凭证");
            cusUserLoginInfo.setErrCode(ErrorCodes.ERROR_LOGIN_WRONG_PASSWORD.getCode());
            cusUserLoginInfo.setLoginStatus(false);
            customerUserLoginInfoService.updateUserLoginInfo(cusUserLoginInfo);

            // 统计密码输入错误后剩余的输入机会次数给客户端
            int leftInputCount = (int) SecurityUtils.getSubject().getSession().getAttribute("leftInputCount");

            ResultDto resultDto = ResultDto.createResult(ErrorCodes.ERROR_LOGIN_EXCESSIVE_ATTEMPTS_LEFT_INPUT_COUNT, leftInputCount);
            Map<String, Object> dataMap = new HashMap<>();
            dataMap.put("leftInputCount", leftInputCount);
            resultDto.setData(dataMap);
            return resultDto;
        } catch (LockedAccountException lae) {
            logger.warn("对用户[" + username + "]进行登录验证..验证未通过,账户已锁定");

            cusUserLoginInfo.setErrCode(ErrorCodes.ERROR_LOGIN_ACCOUNT_LOCKED.getCode());
            cusUserLoginInfo.setLoginStatus(false);
            customerUserLoginInfoService.updateUserLoginInfo(cusUserLoginInfo);

            return ResultDto.createResult(ErrorCodes.ERROR_LOGIN_ACCOUNT_LOCKED);
        } catch (ExcessiveAttemptsException eae) {
            logger.warn("对用户[" + username + "]进行登录验证..验证未通过,错误次数过多");
            return ResultDto.createResult(ErrorCodes.ERROR_LOGIN_EXCESSIVE_ATTEMPTS);
        } catch (CustomerAuthenticationException customerAuthenticationException) {
            System.out.println(subject.isAuthenticated());
            logger.warn("对用户[" + username + "]进行登录验证..验证未通过,堆栈轨迹如下" + customerAuthenticationException.toString());
            ResultDto resultDto = ResultDto.createResult(ErrorCodes.ERROR_LOGIN_CUSTOMER_AUTH_ERR);
            TokenDto tokenDto = null;
            Principal principal = PrincipalUtils.getPrincipal();
            tokenDto = new TokenDto(principal.getAccountName(), subject.getSession().getId().toString(),
                    subject.getSession().getTimeout());
            resultDto.setData(tokenDto);
            return resultDto;
        } catch (CustomerUpdatePasswordException cupe) {
            logger.warn("对用户[" + username + "]进行登录验证..验证未通过,堆栈轨迹如下" + cupe.toString());
            ResultDto resultDto = ResultDto.createResult(ErrorCodes.ERROR_LOGIN_ACCOUNT_NEED_UPDATE_PASSWORD);
            TokenDto tokenDto = null;
            Principal principal = PrincipalUtils.getPrincipal();
            tokenDto = new TokenDto(principal.getAccountName(), subject.getSession().getId().toString(),
                    subject.getSession().getTimeout());
            resultDto.setData(tokenDto);
            return resultDto;
        } catch (AuthenticationException ae) {
            logger.warn("对用户[" + username + "]进行登录验证..验证未通过,堆栈轨迹如下" + ae.toString());
            return ResultDto.createResult(ErrorCodes.ERROR_LOGIN_USERNAME_PASSWORD_ERR);
        }

        TokenDto tokenDto = null;
        if (subject.isAuthenticated()) {
            logger.info("用户[" + username + "]登录认证通过");
            session.setAttribute("login.error.times", 0);
            Principal principal = PrincipalUtils.getPrincipal();
            String ipAddress = getIpAddr(request);
            principal.setIpAddr(ipAddress);
            PrincipalLogUtils.addOperationLog(ModuleNames.CUSTOMER, ModuleNames.CUSTOMER_LOGIN, UserActions.LOGIN,
                    username + "登陆成功");
            putSessionToCache();
            tokenDto = new TokenDto(principal.getAccountName(), subject.getSession().getId().toString(),
                    subject.getSession().getTimeout());
        } else {
            token.clear();
        }
        //获取用户信息
        int userId = PrincipalUtils.getAccountId();
        CusUserSimpleDto cusDto = new CusUserSimpleDto();
        CusUser cusUser = cusUserService.findById(userId);
        CusCustomer cusCustomer = customerService.findCustomerById(cusUser.getRefCustomerId());
        if (ObjectUtils.isEmpty(cusCustomer)) {
            throw Exceptions.bizException(ErrorCodes.ERROR_COMMON, "cusCustomer获取异常");
        }
        tokenDto.setPrimary(cusUser.getIsSystem());//如果客户的isSystem为true，代表的是主账号，取消原来的以名字来做判断。
        CusUserConfigDto cusUserConfigDto = customerUserConfigService.findAllConfigByUserId(userId);
        cusDto.setCusUserConfig(cusUserConfigDto);
        cusDto.setFullName(cusUser.getFullName());
        cusDto.setAccountName(PrincipalUtils.getAccountName());
        cusDto.setUserId(userId);
        cusDto.setUserType(cusCustomer.getUserType());
        subject.getSession().setAttribute("userType", cusDto.getUserType());

        LoginResultDto loginResultDto = new LoginResultDto();
        loginResultDto.setTradingCurrency(cusCustomer.getTradingCurrency());
        if (StringUtils.isNotEmpty(cusUser.getUserAvatar())) {
            List<FileInfoDto> attachments = fileGroupService.getFileDtosByGroupId(cusUser.getUserAvatar());
            cusDto.setFiles(attachments);
            if (attachments != null && attachments.size() > 0) {
                loginResultDto.setAvatar(attachments.get(0).getPath());
            }

        }
        if (tokenDto != null) {
            loginResultDto.setTokenDtoData(tokenDto);
        }
        if (cusDto != null) {
            loginResultDto.setCusUserSimpleDtoData(cusDto);
        }
        ResultDto result = new ResultDto(0, messageSource.getMessage(MessagesCodes.SUCCESSFUL_USER_LOGIN, null));
        result.setData(loginResultDto);
        return result;
    }

    /**
     * 检查是否存在同一个账户多点登陆(踢出先登陆者)
     */
    private void putSessionToCache() {
        Principal principal = PrincipalUtils.getPrincipal();
        Serializable sessionId = SecurityUtils.getSubject().getSession().getId();
        String username = principal.getAccountName();
        onlineSessionManager.addOnlineSession(username, sessionId);
    }

    private boolean checkVCode(String code) {
        if (com.sz.common.base.utils.StringUtils.isNumber(code)) {
            int clientCode = Integer.parseInt(code);
            if (VCodeController.checkVCode(clientCode)) {
                VCodeController.generateNewRandom(); // IMPORTANT!!! refresh server
                return true;
            }
        }
        // IMPORTANT!!! need refresh server side v code
        VCodeController.generateNewRandom();
        return false;
    }

    /**
     * 获取访问者IP 在一般情况下使用Request.getRemoteAddr()即可，但是经过nginx等反向代理软件后，这个方法会失效。
     * 本方法先从Header中获取X-Real-IP，如果不存在再从X-Forwarded-For获得第一个IP(用,分割)，
     * 如果还不存在则调用Request .getRemoteAddr()。
     *
     * @param request
     * @return
     */
    public static String getIpAddr(HttpServletRequest request) throws Exception {
        String ip = request.getHeader("X-Real-IP-Cus-Node");
        if (!StringUtils.isBlank(ip) && !UNKNOWN.equalsIgnoreCase(ip)) {
            return ip;
        }
        ip = request.getHeader("X-Real-IP");
        if (!StringUtils.isBlank(ip) && !UNKNOWN.equalsIgnoreCase(ip)) {
            return ip;
        }

        ip = request.getHeader("X-Forwarded-For");
        if (!StringUtils.isBlank(ip) && !UNKNOWN.equalsIgnoreCase(ip)) {
            // 多次反向代理后会有多个IP值，第一个为真实IP。
            int index = ip.indexOf(',');
            if (index != -1) {
                return ip.substring(0, index);
            } else {
                return ip;
            }
        } else {
            return request.getRemoteAddr();
        }
    }

    @RequestMapping(value = "/logout", method = RequestMethod.GET)
    public ResultDto logout(HttpServletRequest request) throws Exception {
        ResultDto dto = new ResultDto();
        if (SecurityUtils.getSubject().isAuthenticated()) { // 仅退出已登录用户
            String username = (String) SecurityUtils.getSubject().getPrincipal();// getSession().getAttribute("username");
            // 及时将用户注销消息入库
            CusUserLoginInfo userLoginInfo = new CusUserLoginInfo();
            userLoginInfo.setUserName(username);
            userLoginInfo.setUserCode(username);
            String cliIp = getIpAddr(request);
            userLoginInfo.setLoginIp(cliIp);
            userLoginInfo.setLoginStatus(false);
            userLoginInfo.setErrCode(0);
            userLoginInfo.setLoginTime(new Date());

            customerUserLoginInfoService.updateUserLoginInfo(userLoginInfo);
            PrincipalLogUtils.addOperationLog(ModuleNames.CUSTOMER, ModuleNames.CUSTOMER_LOGIN, UserActions.LOGOUT,
                    username + "注销");

            // 进行用户的退出，给出提示信息
            SecurityUtils.getSubject().logout();
            dto.setData("您已安全退出");
        }
        return dto;
    }

    @ApiOperation(value = "判断是否为登录状态", notes = "判断是否为登录状态")
    @RequestMapping(value = "/checkloginstatus", method = RequestMethod.GET)
    public ResultDto isAuthenticated() {
        Subject subject = SecurityUtils.getSubject();
        boolean isAuthenticated = subject.isAuthenticated();
        ResultDto resultDto = new ResultDto();
        resultDto.setData(isAuthenticated);
        return resultDto;
    }

    @ApiOperation(value = "修改密码", notes = "修改密码")
    @RequestMapping(value = "/updatepassword", method = {RequestMethod.PUT})
    public QResultDto updateRandomPassword(@RequestBody SecurityCenterDto securityCenterDto) {
        QResultDto qResultDto = new QResultDto();
        CusUser cusUser = null;
        try {
            Integer cusUserId = PrincipalUtils.getAccountId();
            if (cusUserId > 0) {
                cusUser = cusUserService.findById(cusUserId);
            }
        } catch (Exception ex) {
            throw Exceptions.bizException(ErrorCodes.ERROR_GET_USER_INFO_ERR);
        }
        if (cusUser == null) {
            throw Exceptions.bizException(ErrorCodes.ERROR_GET_USER_INFO_ERR);
        }

        if (!securityCenterDto.getNewPassword().equals(securityCenterDto.getRepeateNewPassword())) {
            throw Exceptions.bizException(com.sz.biz.common.constants.ErrorCodes.ERROR_CUSTOMER_USER_PASSWORD_NOT_MATCH);
        }

        cusUserService.forgetPasswordForReset(cusUser.getId(), decPassword(securityCenterDto.getNewPassword()),
                decPassword(securityCenterDto.getRepeateNewPassword()));
        return qResultDto;
    }

    private String decPassword(String password) {
        String key1 = "DAB019642892EC3231802E13447D3F52";
        String key2 = "14833CB10FAF594B0874F3494AB0946E";
        String key3 = "42099DDB28D27ADFCFD3E961BE6E08D5";
        if (StringUtils.isNotBlank(password)) {
            return Des.getDesTool().strDec(password, securityKeys.getSecurityKey1(), securityKeys.getSecurityKey2(), securityKeys.getSecurityKey3());
        }
        return password;
    }
}
